package grpc

import (
	"context"
	"encoding/json"
	"fmt"
	v1alpha12 "gitverse.ru/synapse/kubelatte/pkg/api/v1alpha1"
	"gitverse.ru/synapse/kubelatte/pkg/grpc/proto"
	"gitverse.ru/synapse/kubelatte/pkg/logs"
	"gitverse.ru/synapse/kubelatte/pkg/opa"
	"gitverse.ru/synapse/kubelatte/pkg/storage"
	"gitverse.ru/synapse/kubelatte/pkg/util"
	"gitverse.ru/synapse/kubelatte/pkg/validate"
	"google.golang.org/grpc"
	corev1 "k8s.io/api/core/v1"
	"net"
)

type ConfigSyncServer struct {
	proto.UnimplementedConfigSyncServer
	LastSyncTime int64
}

func (receiver *ConfigSyncServer) Sync(_ context.Context, message *proto.UpdateConfigEvent) (*proto.EmptyMessage, error) {
	empty := proto.EmptyMessage{}
	receiver.LastSyncTime = message.Time
	return &empty, sendEvent(message.Action, message.Json, message.Type)
}

func (receiver *ConfigSyncServer) Ping(context.Context, *proto.EmptyMessage) (*proto.EmptyMessage, error) {
	return &proto.EmptyMessage{}, nil
}

func (receiver *ConfigSyncServer) LastUpdate(context.Context, *proto.EmptyMessage) (*proto.ServerTime, error) {
	return &proto.ServerTime{Time: receiver.LastSyncTime}, nil
}

func (receiver *ConfigSyncServer) GetScopes(_ context.Context, _ *proto.EmptyMessage) (*proto.Scopes, error) {
	scopes := storage.Storage.GetScopeItems(validate.Validation)
	scopes = append(scopes, storage.Storage.GetScopeItems(validate.Mutation)...)

	var result []byte
	for _, s := range scopes {
		result = append(result, s.Name...)
		result = append(result, ", "...)
	}

	logs.Debug("[ConfigSyncServer] GetScopes, scopes:", scopes)

	return &proto.Scopes{Json: string(result)}, nil
}

func (receiver *ConfigSyncServer) GetTemplates(_ context.Context, _ *proto.EmptyMessage) (*proto.Templates, error) {
	tmpls := storage.Storage.GetTemplates()
	var result []byte
	for t := range tmpls {
		result = append(result, t...)
		result = append(result, ", "...)
	}
	logs.Debug("[ConfigSyncServer] GetTemplates, templates:", string(result))
	return &proto.Templates{Json: string(result)}, nil
}

func (receiver *ConfigSyncServer) GetNamespaces(_ context.Context, _ *proto.EmptyMessage) (*proto.Namespaces, error) {
	namespaces := storage.Storage.GetNamespaces()
	var result []byte
	for ns := range namespaces {
		result = append(result, ns...)
		result = append(result, ", "...)
	}
	logs.Debug("[ConfigSyncServer] GetNamespaces, namespaces:", string(result))
	return &proto.Namespaces{Json: string(result)}, nil
}

func (receiver *ConfigSyncServer) GetTriggers(_ context.Context, _ *proto.EmptyMessage) (*proto.Triggers, error) {
	triggers := storage.Storage.GetTriggers()
	var result []byte
	for tr := range triggers {
		result = append(result, tr...)
		result = append(result, ", "...)
	}
	logs.Debug("[ConfigSyncServer] GetTriggers, triggers:", string(result))
	return &proto.Triggers{Json: string(result)}, nil
}

func sendEvent(action string, data string, tp string) error {
	var err error
	eventName := "Storage%s"

	if action == "update" {
		eventName = fmt.Sprintf(eventName, "Update")
	} else {
		eventName = fmt.Sprintf(eventName, "Remove")
	}

	switch tp {
	case "trigger":
		eventName = eventName + fmt.Sprintf("%sEvent", "Trigger")
		tr := v1alpha12.Trigger{}
		err := json.Unmarshal([]byte(data), &tr)
		if err != nil {
			return err
		}
		err = util.SendEventWithErr(eventName, tr)
	case "template":
		eventName = eventName + fmt.Sprintf("%sEvent", "Template")
		tm := v1alpha12.Template{}
		err := json.Unmarshal([]byte(data), &tm)
		if err != nil {
			return err
		}
		if tm.Spec.Type == "rego" {
			if action == "update" {
				eventName = opa.RegoUpdateTemplate
			} else {
				eventName = opa.RegoDeleteTemplate
			}
		}
		err = util.SendEventWithErr(eventName, tm)
	case "scope":
		eventName = eventName + fmt.Sprintf("%sEvent", "Scope")
		sc := v1alpha12.Scope{}
		err := json.Unmarshal([]byte(data), &sc)
		if err != nil {
			return err
		}
		err = util.SendEventWithErr(eventName, sc)
	case "namespace":
		eventName = eventName + fmt.Sprintf("%sEvent", "Namespace")
		ns := corev1.Namespace{}
		err := json.Unmarshal([]byte(data), &ns)
		if err != nil {
			return err
		}
		err = util.SendEventWithErr(eventName, ns)
	}

	if err != nil {
		return err
	}
	return nil
}

func StartSyncServer(port int) (servShutDown func()) {
	logs.Debugf("[ConfigSyncServer] Start grpc server on addr :%d", port)
	lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", port))
	if err != nil {
		logs.Errorf("[ConfigSyncServer] failed to listen: %v", err)
	}

	var opts []grpc.ServerOption
	grpcServer := grpc.NewServer(opts...)

	go func() {
		proto.RegisterConfigSyncServer(grpcServer, &ConfigSyncServer{})
		err = grpcServer.Serve(lis)
		if err != nil {
			return
		}
	}()

	return grpcServer.GracefulStop
}
